#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
常用函数集合.

@author : lq
@license : Copyright@haohandata
@version : 2.0
"""

import datetime
import json
import os
import re
import socket
import subprocess
import time
from configparser import ConfigParser
from functools import reduce

import util

CONNECT_TIMEOUT = 5
VALID_CONF_TYPES = [
    "install",
    "ipList",
    "nn_collect",
    "public_alarm",
    "public_collect",
    "server_alarm",
    "server_collect",
    "server_nn_collect",
]
SERVER_TYPES = ["server", "equipment", "switch", "dispatch_platform"]
DEVICE_ID_BIT_NUM = 16  # 设备id取MAC地址后几位


def get_conf_file_path(conf_type):
    """返回特定类型的配置文件路径.

    参数：
        type：应该属于几种之一：
            [install, ipList, nn_collect, public_alarm, public_collect, server_alarm,
            server_collect, server_nn_collect]
        如果不是，则会抛出 ValueError
    返回值：
        文件路径字符串
    """
    if conf_type in VALID_CONF_TYPES:
        return os.getenv("INSPECTOR_HOME") + "/conf/{0}.conf".format(conf_type)
    else:
        raise ValueError(conf_type)


def get_file_config_from_path(conf_file_path):
    """根据参数 file_path 返回 config.read() 值."""
    config = ConfigParser()
    config.read(conf_file_path)
    return config


def get_file_config_from_type(conf_type):
    """根据参数 conf_path 返回 config.read() 值."""
    config = ConfigParser()
    config.read(get_conf_file_path(conf_type))
    return config


def get_ip_list_from_conf(server_type):
    """返回从配置文件中读取的 iplist.

    参数：
        server_type:
            为其中之一 ['server', 'equipment', 'switch', 'dispatch_platform']，或者为 'all'

    返回值：
        ip_list：list 类型
    """

    config = get_file_config_from_type("ipList")
    ip_list_str = ""
    if server_type == "all":
        ip_list_str = reduce(
            lambda ip_list_str, cur_type: ip_list_str
            + ","
            + config.get(cur_type, "ip"),
            SERVER_TYPES,
            "",
        )
    else:
        ip_list_str = config.get(server_type, "ip")
    return [ip.strip() for ip in ip_list_str.strip().split(",") if ip]


def get_log_path(software_type):
    """返回 install.conf 中对应软件类型的 log path.

    参数：
        software_type：
            应该严格与 install.conf 中的 _log_path 前的部分一致
    """
    config = get_file_config_from_type("install")
    return config.get("software", software_type + "_log_path")


def get_postgres_connect():
    import psycopg
    import psycopg.extensions

    psycopg.extensions.register_type(psycopg.extensions.UNICODE)
    """从配置文件获得数据库 connect 对象."""
    config = get_file_config_from_type("install")
    return psycopg.connect(
        dbname=config.get("postgreSQL", "dbname"),
        user=config.get("postgreSQL", "username"),
        password=config.get("postgreSQL", "password"),
        host=config.get("postgreSQL", "host"),
        port=config.get("postgreSQL", "port"),
        connect_timeout=CONNECT_TIMEOUT,
    )


def get_sql_select_data(sql):
    """从配置中读取数据库配置，执行 sql 并返回全部结果."""
    with get_postgres_connect() as connect:
        with connect.cursor() as cur:
            cur.execute(sql)
            all_data = cur.fetchall()
    connect.close()
    return all_data


def copy_local_file2db(local_file_path, db_name, sep=",", columns=None):
    """支持将本地文件copy入远程数据库"""
    with get_postgres_connect() as connect:
        with connect.cursor() as cur:
            with open(local_file_path, "r") as f:
                cur.copy_from(f, db_name, sep=sep, columns=columns)


def execute_sql(sql):
    """从配置中读取数据库配置，执行 sql"""
    with get_postgres_connect() as connect:
        with connect.cursor() as cur:
            cur.execute(sql)
    connect.close()


def print_alarm(is_alarm, alarm_type, alarm_detail, host_ip=None, time_seconds=None):
    """输出告警.

    参数：
        is_alarm：是否告警， True 输出 3，False 输出 1
        alarm_type：告警类型
        alarm_detail：告警详细信息
        host_ip: 主机 IP，默认输出当前主机 IP
        time_seconds：时间，默认输出当前时间
    """
    if host_ip is not None and host_ip.find(":") >= 0:
        host_ip = host_ip.split(":")[0]
    print(
        "{0}\t{1}\t{2}\t{3}\t{4}".format(
            "3" if is_alarm else "1",
            time_seconds if time_seconds else time.strftime("%s", time.localtime()),
            host_ip if host_ip else get_host_ip(),
            alarm_type,
            alarm_detail,
        ).strip()
    )


def get_host_ip():
    """返回本机 IP."""
    host_name = socket.getfqdn(socket.gethostname())
    return socket.gethostbyname(host_name)


def get_config_option(section, key):
    config = get_file_config_from_type("install")
    return config.get(section, key)


def get_os_version():
    command = "cat /etc/os-release | grep '^VERSION_ID=' | awk -F = '{print $2}'"
    result = subprocess.run(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
    )
    status = result.returncode
    output = result.stdout.decode("utf-8")
    centos_version = "7"
    if status == 0:
        centos_version = output.replace('"', '')
    return centos_version.strip()


def is_service_running(service):
    command = "service %s status" % (service)
    result = subprocess.run(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
    )
    status = result.returncode
    output = result.stdout.decode("utf-8")
    FLAG_STR_CN = "正在运行"
    FLAG_STR_EN = "is running"
    if status == 0:
        if get_os_version() == "6":
            if FLAG_STR_CN in output or FLAG_STR_EN in output:
                return True
            else:
                return False
        return True
    else:
        return False


def get_logger(logger_file):
    import logging
    from logging.handlers import TimedRotatingFileHandler

    BASE_DIR = os.getenv("INSPECTOR_HOME")
    # 创建一个logger,可以考虑如何将它封装
    logger = logging.getLogger("mylogger")
    logger.setLevel(logging.DEBUG)

    # 创建一个handler，用于写入日志文件
    # fh = logging.FileHandler(os.path.join('%s/log' % (BASE_DIR), logger_file))
    fh = TimedRotatingFileHandler(
        os.path.join("%s/log" % (BASE_DIR), logger_file),
        when="D",
        interval=1,
        backupCount=7,
    )
    fh.setLevel(logging.INFO)

    # 定义handler的输出格式
    formatter = logging.Formatter(
        "%(asctime)s - %(module)s.%(funcName)s.%(lineno)d - %(levelname)s - %(message)s"
    )
    fh.setFormatter(formatter)

    # 给logger添加handler
    logger.addHandler(fh)

    return logger


def print_log(collect_time, table, linelist):
    format_str = ""
    value_array = []
    for i in range(1, len(linelist)):
        format_str = format_str + "{" + str(i - 1) + "}\t"
        fields = linelist[i].split(":")
        if fields[0].lower() == "i_is_restart":
            fields[1] = "true" if int(fields[1].strip()) == 1 else "false"
        value_array.append(fields[1].strip())
    format_str = format_str + table
    print(format_str.format(*value_array))


def get_today_timestamp():
    return int(time.mktime(datetime.date.today().timetuple()))


def get_result_list(file_path, logger, mid_file):
    (last_time, last_line_num) = get_last_timestamp(mid_file)
    collect_time = last_time
    line_num = 0
    try:
        with open(file_path, "r") as f:
            line_num = 0
            for line in f.readlines():
                line_num += 1
                try:
                    # 排查掉空行的影响，UAS会输出空行的日志
                    line = line.strip()
                    if len(line) > 0:
                        linelist = line.split(",")
                        table = linelist[0].split(":")[1].strip()
                        tmp_time = linelist[1].split(":")[1]
                        if tmp_time and int(tmp_time) > get_today_timestamp():
                            collect_time = tmp_time
                            if int(last_time) < int(collect_time) or (
                                int(last_time) == int(collect_time)
                                and line_num > last_line_num
                            ):
                                print_log(collect_time, table, linelist)
                except:
                    err_file = mid_file + ".err"
                    cur_date = time.strftime("%Y%m%d", time.localtime())
                    (last_err_date, last_err_line) = get_last_err_line(err_file)
                    if cur_date > last_err_date or (
                        cur_date == last_err_date and line_num > last_err_line
                    ):
                        logger.error("Format error:%s", line)
                        save_last_err_line(err_file, line_num)
    except IOError:
        logger.info("Log file %s does not exist", file_path)
    finally:
        save_last_time(mid_file, collect_time, line_num)


def get_ip_by_device_id(device_id):
    query_sql = ""
    if DEVICE_ID_BIT_NUM == 16:
        query_sql = "select ip from resource.tma where lower(substring(trim(both from mir_mac) from 13 for 2)\
            ||substring(trim(both from mir_mac) from 16 for 2))=(lpad(to_hex({0}),4,'0'))".format(
            int(device_id)
        )
    else:
        query_sql = "select ip from resource.tma where lower(substring(trim(both from mir_mac) from 16 for 2))\
            =(lpad(to_hex({0}),2,'0'))".format(
            int(device_id)
        )
    result = get_sql_select_data(query_sql)
    return result[0][0].strip() if len(result) > 0 else None


def get_last_timestamp(file_path):
    utc_time = time.strftime("%s", time.localtime())
    last_time = int(utc_time) - 300
    last_line = 0
    try:
        with open(file_path, "r") as f:
            for line in f.readlines():
                if line:
                    fields = line.split("\t")
                    last_time = int(fields[0])
                    last_line = int(fields[1]) if len(fields) > 1 else 0
            return (last_time, last_line)
    except IOError:
        return (last_time, last_line)


def save_last_time(file_path, last_time, line_num):
    try:
        with open(file_path, "w") as f:
            f.write(str(last_time) + "\t" + str(line_num) + "\n")
    except IOError:
        pass


def save_last_err_line(file_path, last_line):
    dateStr = time.strftime("%Y%m%d", time.localtime())
    try:
        with open(file_path, "w+") as f:
            f.write(dateStr + "," + str(last_line) + "\n")
    except IOError:
        pass


def get_last_err_line(file_path):
    """获取上次出错的日期、行数"""
    dateStr = time.strftime("%Y%m%d", time.localtime())
    position = 0
    try:
        with open(file_path, "r") as f:
            for line in f.readlines():
                fields = line.split(",")
                if len(fields) > 1:
                    dateStr = fields[0]
                    position = int(fields[1])
            return (dateStr, position)
    except IOError:
        return (dateStr, position)


def get_last_position(file_path):
    try:
        with open(file_path, "r") as f:
            for line in f.readlines():
                position = int(line)
            return position
    except IOError:
        return 0


def transfer_unit(trafficByte):
    """将5分钟字节数转换为流量单位Gbps"""
    return round(trafficByte * 8.0 * 100 / 300 / 1024 / 1024 / 1024) / 100


def execute_ansible_cmd(ip_list, cmd):
    """执行ansible命令，并返回执行的结果，保存到json对象中"""
    result = {}
    if len(ip_list) > 0:
        inventory = ",".join(ip_list) + ","
        ssh_user = get_config_option("ansible", "ssh_user")
        ssh_password = get_config_option("ansible", "ssh_password")
        ssh_become_password = get_config_option("ansible", "ssh_become_password")
        extra_var = json.dumps(
            {
                "ansible_ssh_pass": ssh_password,
                "ansible_become_pass": ssh_become_password,
            },
        )
        command = 'ansible -i {0} -u {1} -e "{2}" -m shell -a "{3}" all'.format(
            inventory, ssh_user, extra_var, cmd
        )
        result = subprocess.run(
            command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
        )
        status = result.returncode
        output = result.stdout.decode("utf-8")
        if status == 0:
            result = parse_ansible_output(output)
    return result


def parse_ansible_output(output):
    """解析ansible的输出，将结果封装为JSON对象"""
    lines = output.splitlines()
    result = {}
    ip = ""
    for line in lines:
        fields = line.split("|")
        if len(fields) > 1:
            ip = str(fields[0]).strip()
            result[ip] = []
        else:
            if ip != "":
                result[ip].append(line)
    for key in result:
        result[key] = "\n".join(result[key])
    return result


def test_copy_local_file2db():
    """测试是否可以将本地文件copy到远程数据库"""
    """注意：columns中如果只有单个字段，那么需要后面需要接一个逗号"""
    local_file_path = "/tmp/test.csv"
    table_name = "test"
    copy_local_file2db(local_file_path, table_name, columns=("name",))


def test_get_sql_select_data():
    test_sql = "select ip from data.current_alarm where service='TMA流量异常'"
    result = get_sql_select_data(test_sql)
    if result:
        ip_list = map(lambda x: str(x[0]), result)
        print(",".join(ip_list))


def test():
    test_copy_local_file2db()


def get_tma_type_from_db():
    tma_type_map = {}
    query_sql = "select ip,dev_type from resource.tma"
    rows = get_sql_select_data(query_sql)
    for row in rows:
        ip = row[0]
        tma_type = row[1]
        tma_type_map[ip] = tma_type
    return tma_type_map


if __name__ == "__main__":
    test()
